﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Text;
using System.Web.Security;
using AspAuthentication.Core;
using AspAuthentication.Exceptions;
using AspAuthentication.Interfaces;
using AspAuthentication.Persistence;

namespace AspAuthentication.Providers
{
    /// <summary>
    /// Defines the class PortalRoleProvider
    /// </summary>
    public class PortalRoleProvider : RoleProvider, IPortalRoleProvider
    {
        private IUnitOfWork _unitOfWork;
        /// <summary>
        /// Gets or sets the name of the connection string.
        /// </summary>
        /// <value>The name of the connection string.</value>
        public string ConnectionStringName { get; set; }
        /// <summary>
        /// Gets or sets a value indicating whether [create database objects].
        /// </summary>
        /// <value>
        /// 	<c>true</c> if [create database objects]; otherwise, <c>false</c>.
        /// </value>
        public bool CreateDatabaseObjects { get; set; }
        /// <summary>
        /// Gets or sets a value indicating whether [default roles].
        /// </summary>
        /// <value>
        ///   <c>true</c> if [default roles]; otherwise, <c>false</c>.
        /// </value>
        public IList<string> DefaultRoles { get; set; }
        /// <summary>
        /// Gets or sets the name of the application to store and retrieve role information for.
        /// </summary>
        /// <value></value>
        /// <returns>The name of the application to store and retrieve role information for.</returns>
        public override string ApplicationName { get; set; }

        /// <summary>
        /// Gets the unit of work.
        /// </summary>
        /// <value>The unit of work.</value>
        public IUnitOfWork UnitOfWork
        {
            get { return _unitOfWork ?? (_unitOfWork = new UnitOfWork(ConnectionStringName, CreateDatabaseObjects)); }
        }

        /// <summary>
        /// Gets a value indicating whether the specified user is in the specified role for the configured applicationName.
        /// </summary>
        /// <param name="username">The user name to search for.</param>
        /// <param name="roleName">The role to search in.</param>
        /// <returns>
        /// true if the specified user is in the specified role for the configured applicationName; otherwise, false.
        /// </returns>
        public override bool IsUserInRole(string username, string roleName)
        {
            return UnitOfWork.RetrieveDomainObject<DomainUser>(du => du.UserName == username)
                 .DomainRoles.Where(ur => ur.RoleName.Equals(roleName)).Count() > 0;
        }

        /// <summary>
        /// Gets a list of the roles that a specified user is in for the configured applicationName.
        /// </summary>
        /// <param name="username">The user to return a list of roles for.</param>
        /// <returns>
        /// A string array containing the names of all the roles that the specified user is in for the configured applicationName.
        /// </returns>
        public override string[] GetRolesForUser(string username)
        {
            return UnitOfWork.RetrieveDomainObject<DomainUser>(du => du.UserName == username)
                   .DomainRoles.Select(domainRole => domainRole.RoleName).ToArray();
        }

        /// <summary>
        /// Adds a new role to the data source for the configured applicationName.
        /// </summary>
        /// <param name="roleName">The name of the role to create.</param>
        public override void CreateRole(string roleName)
        {
            UnitOfWork.Create(new DomainRole { RoleName = roleName });
        }

        /// <summary>
        /// Removes a role from the data source for the configured applicationName.
        /// </summary>
        /// <param name="roleName">The name of the role to delete.</param>
        /// <param name="throwOnPopulatedRole">If true, throw an exception if <paramref name="roleName"/> has one or more members and do not delete <paramref name="roleName"/>.</param>
        /// <returns>
        /// true if the role was successfully deleted; otherwise, false.
        /// </returns>
        public override bool DeleteRole(string roleName, bool throwOnPopulatedRole)
        {
            UnitOfWork.DeleteDomainRole(roleName);
            return true;
        }

        /// <summary>
        /// Gets a value indicating whether the specified role name already exists in the role data source for the configured applicationName.
        /// </summary>
        /// <param name="roleName">The name of the role to search for in the data source.</param>
        /// <returns>
        /// true if the role name already exists in the data source for the configured applicationName; otherwise, false.
        /// </returns>
        public override bool RoleExists(string roleName)
        {
            try
            {
                UnitOfWork.RetrieveDomainObject<DomainRole>(r => r.RoleName == roleName);
            }
            catch (EntityNotFoundException)
            {
                return false;
            }
            return true;
        }

        /// <summary>
        /// Adds the specified user names to the specified roles for the configured applicationName.
        /// </summary>
        /// <param name="usernames">A string array of user names to be added to the specified roles.</param>
        /// <param name="roleNames">A string array of the role names to add the specified user names to.</param>
        public override void AddUsersToRoles(string[] usernames, string[] roleNames)
        {
            var stringBuilder = new StringBuilder();
            foreach (var user in usernames)
            {
                foreach (var roleName in roleNames)
                {
                    stringBuilder.AppendFormat("EXECUTE [dbo].[InsertDomainRoleDomainUsers] @roleName='{0}',@userName='{1}';", roleName, user);
                }
            }
            if (stringBuilder.Length > 0)
            {
                UnitOfWork.ExecuteSql(stringBuilder.ToString());
            }
        }

        /// <summary>
        /// Removes the specified user names from the specified roles for the configured applicationName.
        /// </summary>
        /// <param name="usernames">A string array of user names to be removed from the specified roles.</param>
        /// <param name="roleNames">A string array of role names to remove the specified user names from.</param>
        public override void RemoveUsersFromRoles(string[] usernames, string[] roleNames)
        {
            var stringBuilder = new StringBuilder();
            foreach (var user in usernames)
            {
                foreach (var roleName in roleNames)
                {
                    stringBuilder.AppendFormat("EXECUTE [dbo].[DeleteDomainRoleDomainUsers] @roleName='{0}',@userName='{1}';", roleName, user);
                }
            }
            if (stringBuilder.Length > 0)
            {
                UnitOfWork.ExecuteSql(stringBuilder.ToString());
            }
        }

        /// <summary>
        /// Gets a list of users in the specified role for the configured applicationName.
        /// </summary>
        /// <param name="roleName">The name of the role to get the list of users for.</param>
        /// <returns>
        /// A string array containing the names of all the users who are members of the specified role for the configured applicationName.
        /// </returns>
        public override string[] GetUsersInRole(string roleName)
        {
            return UnitOfWork.Search<DomainUser>(u =>
                                    u.DomainRoles.Any(r => r.RoleName == roleName))
                              .Select(user => user.UserName).ToArray();
        }

        /// <summary>
        /// Gets a list of all the roles for the configured applicationName.
        /// </summary>
        /// <returns>
        /// A string array containing the names of all the roles stored in the data source for the configured applicationName.
        /// </returns>
        public override string[] GetAllRoles()
        {
            return UnitOfWork.Search<DomainRole>().Select(r => r.RoleName).ToArray();
        }

        /// <summary>
        /// Gets an array of user names in a role where the user name contains the specified user name to match.
        /// </summary>
        /// <param name="roleName">The role to search in.</param>
        /// <param name="usernameToMatch">The user name to search for.</param>
        /// <returns>
        /// A string array containing the names of all the users where the user name matches <paramref name="usernameToMatch"/> and the user is a member of the specified role.
        /// </returns>
        public override string[] FindUsersInRole(string roleName, string usernameToMatch)
        {
            return UnitOfWork.Search<DomainUser>(u =>
                                u.UserName.Contains(usernameToMatch) &&
                                u.DomainRoles.Any(r => r.RoleName == roleName))
                             .Select(u => u.UserName).ToArray();
        }

        /// <summary>
        /// Initializes the provider.
        /// </summary>
        /// <param name="name">The friendly name of the provider.</param>
        /// <param name="config">A collection of the name/value pairs representing the provider-specific attributes specified in the configuration for this provider.</param>
        /// <exception cref="T:System.ArgumentNullException">The name of the provider is null.</exception>
        /// <exception cref="T:System.ArgumentException">The name of the provider has a length of zero.</exception>
        /// <exception cref="T:System.InvalidOperationException">An attempt is made to call <see cref="M:System.Configuration.Provider.ProviderBase.Initialize(System.String,System.Collections.Specialized.NameValueCollection)"/> on a provider after the provider has already been initialized.</exception>
        public override void Initialize(string name, NameValueCollection config)
        {
            base.Initialize(name, config);
            ApplicationName = ConfigHelper.SafeConfigRead("applicationName", config);
            ConnectionStringName = ConfigHelper.SafeConfigRead("connectionStringName", config);
            CreateDatabaseObjects = ConfigHelper.SafeConfigBooleanRead("createDatabaseObjects", config, false);

            var roles = ConfigHelper.SafeConfigRead("defaultRoles", config, null);
            DefaultRoles = new List<string>();
            if (!string.IsNullOrWhiteSpace(roles))
            {
                var r = roles.Split(new[] { ";" }, StringSplitOptions.RemoveEmptyEntries);
                foreach (var s in r.Where(s => s.Trim().Length > 0))
                {
                    DefaultRoles.Add(s);
                    if (!RoleExists(s)) { CreateRole(s); }
                }
            }
        }

    }
}